// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package reflectdata

import (
	"encoding/binary"
	"fmt"
	"os"
	"sort"
	"strings"
	"sync"

	"cmd/compile/internal/base"
	"cmd/compile/internal/bitvec"
	"cmd/compile/internal/escape"
	"cmd/compile/internal/inline"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/objw"
	"cmd/compile/internal/staticdata"
	"cmd/compile/internal/typebits"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/internal/gcprog"
	"cmd/internal/obj"
	"cmd/internal/objabi"
	"cmd/internal/src"
)

type ptabEntry struct {
	s *types.Sym
	t *types.Type
}

func CountPTabs() int {
	return len(ptabs)
}

// 运行时接口和反射数据结构
var (
	// 保护签名集和签名片
	signatmu sync.Mutex
	// 跟踪哪些类型需要运行时类型描述符
	signatset = make(map[*types.Type]struct{})
	// 等待生成的类型队列运行时类型描述符
	signatslice []typeAndStr

	gcsymmu  sync.Mutex // 保护gcsymset和GCSYMSICLE 
	gcsymset = make(map[*types.Type]struct{})

	ptabs []*ir.Name
)

type typeSig struct {
	name  *types.Sym
	isym  *obj.LSym
	tsym  *obj.LSym
	type_ *types.Type
	mtype *types.Type
}

// 为
// 给定的映射类型。此类型对用户不可见-
// 我们只包含足够的信息来为其生成正确的GC 
// 程序。
// 确保它与runtime/map保持同步。去
const (
	BUCKETSIZE  = 8
	MAXKEYSIZE  = 128
	MAXELEMSIZE = 128
)

func structfieldSize() int { return 3 * types.PtrSize }       // Sizeof（runtime.structfield{}）
func imethodSize() int     { return 4 + 4 }                   // Sizeof（runtime.imethod{}）
func commonSize() int      { return 4*types.PtrSize + 8 + 8 } // Sizeof（runtime.type{}）

func uncommonSize(t *types.Type) int { // Sizeof（runtime.uncomontype{}）
	if t.Sym() == nil && len(methods(t)) == 0 {
		return 0
	}
	return 4 + 2 + 2 + 4 + 4
}

func makefield(name string, t *types.Type) *types.Field {
	sym := (*types.Pkg)(nil).Lookup(name)
	return types.NewField(src.NoXPos, sym, t)
}

// MapBucketType根据地图的类型生成地图桶类型。
func MapBucketType(t *types.Type) *types.Type {
	if t.MapType().Bucket != nil {
		return t.MapType().Bucket
	}

	keytype := t.Key()
	elemtype := t.Elem()
	types.CalcSize(keytype)
	types.CalcSize(elemtype)
	if keytype.Size() > MAXKEYSIZE {
		keytype = types.NewPtr(keytype)
	}
	if elemtype.Size() > MAXELEMSIZE {
		elemtype = types.NewPtr(elemtype)
	}

	field := make([]*types.Field, 0, 5)

	// 第一个字段是：uint8 topbits[BUCKETSIZE]。
	arr := types.NewArray(types.Types[types.TUINT8], BUCKETSIZE)
	field = append(field, makefield("topbits", arr))

	arr = types.NewArray(keytype, BUCKETSIZE)
	arr.SetNoalg(true)
	keys := makefield("keys", arr)
	field = append(field, keys)

	arr = types.NewArray(elemtype, BUCKETSIZE)
	arr.SetNoalg(true)
	elems := makefield("elems", arr)
	field = append(field, elems)

	// 如果键和元素没有指针，映射实现
	// 可以在旁边保留一个溢出指针列表，以便
	// 可以将存储桶标记为没有指针。
	// 在本例中，通过将溢出字段的类型更改为uintpttr，使bucket没有指针。
	// 参见hmap评论。运行时/映射溢出。去
	otyp := types.Types[types.TUNSAFEPTR]
	if !elemtype.HasPointers() && !keytype.HasPointers() {
		otyp = types.Types[types.TUINTPTR]
	}
	overflow := makefield("overflow", otyp)
	field = append(field, overflow)

	// 链接字段
	bucket := types.NewStruct(types.NoPkg, field[:])
	bucket.SetNoalg(true)
	types.CalcSize(bucket)

	// 检查映射代码所依赖的不变量。
	if !types.IsComparable(t.Key()) {
		base.Fatalf("unsupported map key type for %v", t)
	}
	if BUCKETSIZE < 8 {
		base.Fatalf("bucket size too small for proper alignment")
	}
	if uint8(keytype.Alignment()) > BUCKETSIZE {
		base.Fatalf("key align too big for %v", t)
	}
	if uint8(elemtype.Alignment()) > BUCKETSIZE {
		base.Fatalf("elem align too big for %v", t)
	}
	if keytype.Size() > MAXKEYSIZE {
		base.Fatalf("key size to large for %v", t)
	}
	if elemtype.Size() > MAXELEMSIZE {
		base.Fatalf("elem size to large for %v", t)
	}
	if t.Key().Size() > MAXKEYSIZE && !keytype.IsPtr() {
		base.Fatalf("key indirect incorrect for %v", t)
	}
	if t.Elem().Size() > MAXELEMSIZE && !elemtype.IsPtr() {
		base.Fatalf("elem indirect incorrect for %v", t)
	}
	if keytype.Size()%keytype.Alignment() != 0 {
		base.Fatalf("key size not a multiple of key align for %v", t)
	}
	if elemtype.Size()%elemtype.Alignment() != 0 {
		base.Fatalf("elem size not a multiple of elem align for %v", t)
	}
	if uint8(bucket.Alignment())%uint8(keytype.Alignment()) != 0 {
		base.Fatalf("bucket align not multiple of key align %v", t)
	}
	if uint8(bucket.Alignment())%uint8(elemtype.Alignment()) != 0 {
		base.Fatalf("bucket align not multiple of elem align %v", t)
	}
	if keys.Offset%keytype.Alignment() != 0 {
		base.Fatalf("bad alignment of keys in bmap for %v", t)
	}
	if elems.Offset%elemtype.Alignment() != 0 {
		base.Fatalf("bad alignment of elems in bmap for %v", t)
	}

	// 再次检查溢出字段是否是struct中的最终内存，
	// 结尾没有填充。
	if overflow.Offset != bucket.Size()-int64(types.PtrSize) {
		base.Fatalf("bad offset of overflow in bmap for %v", t)
	}

	t.MapType().Bucket = bucket

	bucket.StructType().Map = t
	return bucket
}

// MapType为给定的映射类型构建一个表示Hmap结构的类型。
// 确保它与runtime/map保持同步。去
func MapType(t *types.Type) *types.Type {
	if t.MapType().Hmap != nil {
		return t.MapType().Hmap
	}

	bmap := MapBucketType(t)

	// 构建结构：
	// 键入hmap结构{
	// count int 
	// flags uint8 
	// B uint8 
	// noverflow uint16 
	// hash0 uint32 
	// bucket*bmap 
	// nevacuate uintpr 
	// 额外不安全。指针
	// /}
	// 必须与运行时/映射匹配。去：嗯。用于散步。去找OMAKEMAP。用于散步。去找OMAKEMAP。
	fields := []*types.Field{
		makefield("count", types.Types[types.TINT]),
		makefield("flags", types.Types[types.TUINT8]),
		makefield("B", types.Types[types.TUINT8]),
		makefield("noverflow", types.Types[types.TUINT16]),
		makefield("oldbuckets", types.NewPtr(bmap)),
		makefield("nevacuate", types.Types[types.TUINTPTR]),
		makefield("extra", types.Types[types.TUNSAFEPTR]),
	}

	hmap := types.NewStruct(types.NoPkg, fields)
	hmap.SetNoalg(true)
	types.CalcSize(hmap)

	// 在64位
	// 平台上，hmap的大小应为48字节，在32位平台上，hmap的大小应为28字节。
	if size := int64(8 + 5*types.PtrSize); hmap.Size() != size {
		base.Fatalf("hmap size not correct: got %d, want %d", hmap.Size(), size)
	}

	t.MapType().Hmap = hmap
	hmap.StructType().Map = t
	return hmap
}

// MapIterType为给定的映射类型构建一个表示Hiter结构的类型。
// 确保它与runtime/map保持同步。去
func MapIterType(t *types.Type) *types.Type {
	if t.MapType().Hiter != nil {
		return t.MapType().Hiter
	}

	hmap := MapType(t)
	bmap := MapBucketType(t)

	// 构建结构：
	// 键入hiter结构{
	// key*key 
	// elem*elem 
	// t不安全。指针
	// h*hmap 
	// buckets*bmap 
	// bptr bmap 
	// 溢出不安全。指针
	// offset uint8 
	// wrapped bool 
	// B uint8 
	// i uint8 
	// bucket uintpr 
	// checkBucket uintpr 
	// /}
	fields := []*types.Field{
		makefield("key", types.NewPtr(t.Key())),   // 用于射程。去TMAP。
		makefield("elem", types.NewPtr(t.Elem())), // 用于射程。去TMAP。
		makefield("t", types.Types[types.TUNSAFEPTR]),
		makefield("h", types.NewPtr(hmap)),
		makefield("buckets", types.NewPtr(bmap)),
		makefield("bptr", types.NewPtr(bmap)),
		makefield("overflow", types.Types[types.TUNSAFEPTR]),
		makefield("oldoverflow", types.Types[types.TUNSAFEPTR]),
		makefield("startBucket", types.Types[types.TUINTPTR]),
		makefield("offset", types.Types[types.TUINT8]),
		makefield("wrapped", types.Types[types.TBOOL]),
		makefield("B", types.Types[types.TUINT8]),
		makefield("i", types.Types[types.TUINT8]),
		makefield("bucket", types.Types[types.TUINTPTR]),
		makefield("checkBucket", types.Types[types.TUINTPTR]),
	}

	// 包含上述字段的build iterator struct 
	hiter := types.NewStruct(types.NoPkg, fields)
	hiter.SetNoalg(true)
	types.CalcSize(hiter)
	if hiter.Size() != int64(12*types.PtrSize) {
		base.Fatalf("hash_iter size not correct %d %d", hiter.Size(), 12*types.PtrSize)
	}
	t.MapType().Hiter = hiter
	hiter.StructType().Map = t
	return hiter
}

// methods返回非接口类型t的方法，按名称排序。
// 根据需要生成存根函数。
func methods(t *types.Type) []*typeSig {
	if t.HasShape() {
		// 形状类型没有方法。
		return nil
	}
	// 方法类型
	mt := types.ReceiverBaseType(t)

	if mt == nil {
		return nil
	}
	typecheck.CalcMethods(mt)

	// 列出t的方法，
	// 必要时生成代码。
	var ms []*typeSig
	for _, f := range mt.AllMethods().Slice() {
		if f.Sym == nil {
			base.Fatalf("method with no sym on %v", mt)
		}
		if !f.IsMethod() {
			base.Fatalf("non-method on %v method %v %v", mt, f.Sym, f)
		}
		if f.Type.Recv() == nil {
			base.Fatalf("receiver with no type on %v method %v %v", mt, f.Sym, f)
		}
		if f.Nointerface() && !t.IsFullyInstantiated() {
			// 如果f不是接口，则跳过创建方法包装。但是，如果
			// t是一个实例化类型，我们仍然需要调用
			// methodWrapper，因为methodWrapper也会在该类型上生成实际的
			// generic方法。
			continue
		}

		// 获取此特定方法的接收器类型。
		// 如果是指针接收器，但不是指针t和
		// 这不是结构中的嵌入式指针，
		// 方法不适用。
		if !types.IsMethodApplicable(t, f) {
			continue
		}

		sig := &typeSig{
			name:  f.Sym,
			isym:  methodWrapper(t, f, true),
			tsym:  methodWrapper(t, f, false),
			type_: typecheck.NewMethodType(f.Type, t),
			mtype: typecheck.NewMethodType(f.Type, nil),
		}
		if f.Nointerface() {
			// 在实例化的
			// type上使用nointerface方法时，不要实际应用typeSig。
			continue
		}
		ms = append(ms, sig)
	}

	return ms
}

// imethods返回按名称排序的接口类型t的方法。
func imethods(t *types.Type) []*typeSig {
	var methods []*typeSig
	for _, f := range t.AllMethods().Slice() {
		if f.Type.Kind() != types.TFUNC || f.Sym == nil {
			continue
		}
		if f.Sym.IsBlank() {
			base.Fatalf("unexpected blank symbol in interface method set")
		}
		if n := len(methods); n > 0 {
			last := methods[n-1]
			if !last.name.Less(f.Sym) {
				base.Fatalf("sigcmp vs sortinter %v %v", last.name, f.Sym)
			}
		}

		sig := &typeSig{
			name:  f.Sym,
			mtype: f.Type,
			type_: typecheck.NewMethodType(f.Type, nil),
		}
		methods = append(methods, sig)

		// 注意（rsc）：可能是一个疏忽导致了
		// IfaceType。方法不在反射数据中。
		// 生成方法体，以便编译后的
		// 代码可以引用它。
		methodWrapper(t, f, false)
	}

	return methods
}

func dimportpath(p *types.Pkg) {
	if p.Pathsym != nil {
		return
	}

	// 如果我们正在编译运行时包，那么围绕
	// /--localpkg和Pkgs有两个运行时包。运行时。我们不想为
	// /两者都生成导入路径符号，所以只需为localpkg生成一个即可。
	if base.Ctxt.Pkgpath == "runtime" && p == ir.Pkgs.Runtime {
		return
	}

	str := p.Path
	if p == types.LocalPkg {
		// 注意：myimportpath！=“”或dgopkgpath不会调用dimportpath。
		str = base.Ctxt.Pkgpath
	}

	s := base.Ctxt.Lookup("type..importpath." + p.Prefix + ".")
	ot := dnameData(s, 0, str, "", nil, false)
	objw.Global(s, int32(ot), obj.DUPOK|obj.RODATA)
	s.Set(obj.AttrContentAddressable, true)
	p.Pathsym = s
}

func dgopkgpath(s *obj.LSym, ot int, pkg *types.Pkg) int {
	if pkg == nil {
		return objw.Uintptr(s, ot, 0)
	}

	if pkg == types.LocalPkg && base.Ctxt.Pkgpath == "" {
		// 如果我们不知道正在编译的包的完整导入路径
		// （即，-p未在编译器命令行上传递），请发出对
		// 类型的引用。。importpath。""., 链接器将使用正确的导入路径重写。
		// 每个导入这个包的包都直接定义了符号。
		// 另请参见https:
		ns := base.Ctxt.Lookup(`type..importpath."".`)
		return objw.SymPtr(s, ot, ns, 0)
	}

	dimportpath(pkg)
	return objw.SymPtr(s, ot, pkg.Pathsym, 0)
}

// dgopkgpathOff将偏移量ot处的s中的偏移量重新定位写入pkg路径符号。
func dgopkgpathOff(s *obj.LSym, ot int, pkg *types.Pkg) int {
	if pkg == nil {
		return objw.Uint32(s, ot, 0)
	}
	if pkg == types.LocalPkg && base.Ctxt.Pkgpath == "" {
		// 如果我们不知道正在编译的包的完整导入路径
		// （即，-p未在编译器命令行上传递），请发出对
		// 类型的引用。。importpath。""., 链接器将使用正确的导入路径重写。
		// 每个导入这个包的包都直接定义了符号。
		// 另请参见https:
		ns := base.Ctxt.Lookup(`type..importpath."".`)
		return objw.SymPtrOff(s, ot, ns)
	}

	dimportpath(pkg)
	return objw.SymPtrOff(s, ot, pkg.Pathsym)
}

// dnameField转储一个反射。结构字段的名称。
func dnameField(lsym *obj.LSym, ot int, spkg *types.Pkg, ft *types.Field) int {
	if !types.IsExported(ft.Sym.Name) && ft.Sym.Pkg != spkg {
		base.Fatalf("package mismatch for %v", ft.Sym)
	}
	nsym := dname(ft.Sym.Name, ft.Note, nil, types.IsExported(ft.Sym.Name))
	return objw.SymPtr(lsym, ot, nsym, 0)
}

// dnameData写入反射的内容。在偏移量ot处输入s。
func dnameData(s *obj.LSym, ot int, name, tag string, pkg *types.Pkg, exported bool) int {
	if len(name) >= 1<<29 {
		base.Fatalf("name too long: %d %s...", len(name), name[:1024])
	}
	if len(tag) >= 1<<29 {
		base.Fatalf("tag too long: %d %s...", len(tag), tag[:1024])
	}
	var nameLen [binary.MaxVarintLen64]byte
	nameLenLen := binary.PutUvarint(nameLen[:], uint64(len(name)))
	var tagLen [binary.MaxVarintLen64]byte
	tagLenLen := binary.PutUvarint(tagLen[:], uint64(len(tag)))

	// 编码名称和标签。请参见反射/键入。了解细节。
	var bits byte
	l := 1 + nameLenLen + len(name)
	if exported {
		bits |= 1 << 0
	}
	if len(tag) > 0 {
		l += tagLenLen + len(tag)
		bits |= 1 << 1
	}
	if pkg != nil {
		bits |= 1 << 2
	}
	b := make([]byte, l)
	b[0] = bits
	copy(b[1:], nameLen[:nameLenLen])
	copy(b[1+nameLenLen:], name)
	if len(tag) > 0 {
		tb := b[1+nameLenLen+len(name):]
		copy(tb, tagLen[:tagLenLen])
		copy(tb[tagLenLen:], tag)
	}

	ot = int(s.WriteBytes(base.Ctxt, int64(ot), b))

	if pkg != nil {
		ot = dgopkgpathOff(s, ot, pkg)
	}

	return ot
}

var dnameCount int

// dname创建一个反射。结构域或方法的名称。
func dname(name, tag string, pkg *types.Pkg, exported bool) *obj.LSym {
	// 将数据写为“类型…”要向
	// 链接器发送两个信号，第一个是动态链接时，符号
	// 应该移动到一个relro部分，第二个是
	// 内容不应该被解码为一个类型。
	sname := "type..namedata."
	if pkg == nil {
		// 在常见情况下，与其他软件包共享数据。
		if name == "" {
			if exported {
				sname += "-noname-exported." + tag
			} else {
				sname += "-noname-unexported." + tag
			}
		} else {
			if exported {
				sname += name + "." + tag
			} else {
				sname += name + "-" + tag
			}
		}
	} else {
		sname = fmt.Sprintf(`%s"".%d`, sname, dnameCount)
		dnameCount++
	}
	s := base.Ctxt.Lookup(sname)
	if len(s.P) > 0 {
		return s
	}
	ot := dnameData(s, 0, name, tag, pkg, exported)
	objw.Global(s, int32(ot), obj.DUPOK|obj.RODATA)
	s.Set(obj.AttrContentAddressable, true)
	return s
}

// dextratype转储运行时的字段。取消MonType。
// dataAdd是写入[]方法字段的
// 后备数组的头之后的偏移量，以字节为单位（由dextratypeData）。
func dextratype(lsym *obj.LSym, ot int, t *types.Type, dataAdd int) int {
	m := methods(t)
	if t.Sym() == nil && len(m) == 0 {
		return ot
	}
	noff := int(types.Rnd(int64(ot), int64(types.PtrSize)))
	if noff != ot {
		base.Fatalf("unexpected alignment in dextratype for %v", t)
	}

	for _, a := range m {
		writeType(a.type_)
	}

	ot = dgopkgpathOff(lsym, ot, typePkg(t))

	dataAdd += uncommonSize(t)
	mcount := len(m)
	if mcount != int(uint16(mcount)) {
		base.Fatalf("too many methods on %v: %d", t, mcount)
	}
	xcount := sort.Search(mcount, func(i int) bool { return !types.IsExported(m[i].name.Name) })
	if dataAdd != int(uint32(dataAdd)) {
		base.Fatalf("methods are too far away on %v: %d", t, dataAdd)
	}

	ot = objw.Uint16(lsym, ot, uint16(mcount))
	ot = objw.Uint16(lsym, ot, uint16(xcount))
	ot = objw.Uint32(lsym, ot, uint32(dataAdd))
	ot = objw.Uint32(lsym, ot, 0)
	return ot
}

func typePkg(t *types.Type) *types.Pkg {
	tsym := t.Sym()
	if tsym == nil {
		switch t.Kind() {
		case types.TARRAY, types.TSLICE, types.TPTR, types.TCHAN:
			if t.Elem() != nil {
				tsym = t.Elem().Sym()
			}
		}
	}
	if tsym != nil && tsym.Pkg != types.BuiltinPkg {
		return tsym.Pkg
	}
	return nil
}

// dextratypeData转储
// 运行时的[]方法字段的备份数组。取消MonType。
func dextratypeData(lsym *obj.LSym, ot int, t *types.Type) int {
	for _, a := range methods(t) {
		// 运行时/类型。go:/method 
		exported := types.IsExported(a.name.Name)
		var pkg *types.Pkg
		if !exported && a.name.Pkg != typePkg(t) {
			pkg = a.name.Pkg
		}
		nsym := dname(a.name.Name, "", pkg, exported)

		ot = objw.SymPtrOff(lsym, ot, nsym)
		ot = dmethodptrOff(lsym, ot, writeType(a.mtype))
		ot = dmethodptrOff(lsym, ot, a.isym)
		ot = dmethodptrOff(lsym, ot, a.tsym)
	}
	return ot
}

func dmethodptrOff(s *obj.LSym, ot int, x *obj.LSym) int {
	objw.Uint32(s, ot, 0)
	r := obj.Addrel(s)
	r.Off = int32(ot)
	r.Siz = 4
	r.Sym = x
	r.Type = objabi.R_METHODOFF
	return ot + 4
}

var kinds = []int{
	types.TINT:        objabi.KindInt,
	types.TUINT:       objabi.KindUint,
	types.TINT8:       objabi.KindInt8,
	types.TUINT8:      objabi.KindUint8,
	types.TINT16:      objabi.KindInt16,
	types.TUINT16:     objabi.KindUint16,
	types.TINT32:      objabi.KindInt32,
	types.TUINT32:     objabi.KindUint32,
	types.TINT64:      objabi.KindInt64,
	types.TUINT64:     objabi.KindUint64,
	types.TUINTPTR:    objabi.KindUintptr,
	types.TFLOAT32:    objabi.KindFloat32,
	types.TFLOAT64:    objabi.KindFloat64,
	types.TBOOL:       objabi.KindBool,
	types.TSTRING:     objabi.KindString,
	types.TPTR:        objabi.KindPtr,
	types.TSTRUCT:     objabi.KindStruct,
	types.TINTER:      objabi.KindInterface,
	types.TCHAN:       objabi.KindChan,
	types.TMAP:        objabi.KindMap,
	types.TARRAY:      objabi.KindArray,
	types.TSLICE:      objabi.KindSlice,
	types.TFUNC:       objabi.KindFunc,
	types.TCOMPLEX64:  objabi.KindComplex64,
	types.TCOMPLEX128: objabi.KindComplex128,
	types.TUNSAFEPTR:  objabi.KindUnsafePointer,
}

// tflag记录在reflect/type中。去
// 
// tflag值必须与以下文件中的副本保持同步：
// cmd/compile/internal/reflectdata/reflect。转到
// cmd/link/internal/ld/decodesym。转到
// 反射/键入。转到
// 运行时/类型。go 
const (
	tflagUncommon      = 1 << 0
	tflagExtraStar     = 1 << 1
	tflagNamed         = 1 << 2
	tflagRegularMemory = 1 << 3
)

var (
	memhashvarlen  *obj.LSym
	memequalvarlen *obj.LSym
)

// dcommontype转储反射的内容。rtype（运行时类型）。
func dcommontype(lsym *obj.LSym, t *types.Type) int {
	types.CalcSize(t)
	eqfunc := geneq(t)

	sptrWeak := true
	var sptr *obj.LSym
	if !t.IsPtr() || t.IsPtrElem() {
		tptr := types.NewPtr(t)
		if t.Sym() != nil || methods(tptr) != nil {
			sptrWeak = false
		}
		sptr = writeType(tptr)
	}

	gcsym, useGCProg, ptrdata := dgcsym(t, true)
	delete(gcsymset, t)

	// 思考/打字。开始：/^键入。rtype 
	// 实际类型结构
	// 类型rtype struct{
	// size uintptr 
	// ptrdata uintptr 
	// hash uint32 
	// tflag tflag 
	// align uint8 
	// FIELDRALIGN uint8 
	// kind uint8 
	// equal func（unsafe.Pointer，unsafe.Pointer，unsafe.Pointer）bool 
	// gcdata*byte 
	// str nameOff 
	// ptrToThis typeOff 
	// }
	ot := 0
	ot = objw.Uintptr(lsym, ot, uint64(t.Size()))
	ot = objw.Uintptr(lsym, ot, uint64(ptrdata))
	ot = objw.Uint32(lsym, ot, types.TypeHash(t))

	var tflag uint8
	if uncommonSize(t) != 0 {
		tflag |= tflagUncommon
	}
	if t.Sym() != nil && t.Sym().Name != "" {
		tflag |= tflagNamed
	}
	if isRegularMemory(t) {
		tflag |= tflagRegularMemory
	}

	exported := false
	p := t.NameString()
	// 如果我们写出类型T，
	// 我们很可能也写出类型*T。
	// 使用字符串“*T”[1:]表示“T”，以便两个
	// 共享存储。这是减少反射字符串占用的
	// 空间量的廉价方法。
	if !strings.HasPrefix(p, "*") {
		p = "*" + p
		tflag |= tflagExtraStar
		if t.Sym() != nil {
			exported = types.IsExported(t.Sym().Name)
		}
	} else {
		if t.Elem() != nil && t.Elem().Sym() != nil {
			exported = types.IsExported(t.Elem().Sym().Name)
		}
	}

	ot = objw.Uint8(lsym, ot, tflag)

	// 运行时（和常识）希望对齐是二的幂。
	i := int(uint8(t.Alignment()))

	if i == 0 {
		i = 1
	}
	if i&(i-1) != 0 {
		base.Fatalf("invalid alignment %d for %v", uint8(t.Alignment()), t)
	}
	ot = objw.Uint8(lsym, ot, uint8(t.Alignment())) // align 
	ot = objw.Uint8(lsym, ot, uint8(t.Alignment())) // fieldAlign 

	i = kinds[t.Kind()]
	if types.IsDirectIface(t) {
		i |= objabi.KindDirectIface
	}
	if useGCProg {
		i |= objabi.KindGCProg
	}
	ot = objw.Uint8(lsym, ot, uint8(i)) // kind 
	if eqfunc != nil {
		ot = objw.SymPtr(lsym, ot, eqfunc, 0) // equality function 
	} else {
		ot = objw.Uintptr(lsym, ot, 0) // type we can't do==with 
	}
	ot = objw.SymPtr(lsym, ot, gcsym, 0) // gcdata 

	nsym := dname(p, "", nil, exported)
	ot = objw.SymPtrOff(lsym, ot, nsym) // str 
	// ptTothis 
	if sptr == nil {
		ot = objw.Uint32(lsym, ot, 0)
	} else if sptrWeak {
		ot = objw.SymPtrWeakOff(lsym, ot, sptr)
	} else {
		ot = objw.SymPtrOff(lsym, ot, sptr)
	}

	return ot
}

// TrackSym返回用于跟踪字段/方法f的符号，假设
// 是结构/接口类型t的成员。
func TrackSym(t *types.Type, f *types.Field) *obj.LSym {
	return base.PkgLinksym("go.track", t.LinkString()+"."+f.Sym.Name, obj.ABI0)
}

func TypeSymPrefix(prefix string, t *types.Type) *types.Sym {
	p := prefix + "." + t.LinkString()
	s := types.TypeSymLookup(p)

	// 此函数用于查找与类型相关的生成函数
	// （例如eq和hash）。确保它们确实是生成的。
	signatmu.Lock()
	NeedRuntimeType(t)
	signatmu.Unlock()

	// 打印（“algsym:%s->%+s\n”，p，s）；

	return s
}

func TypeSym(t *types.Type) *types.Sym {
	if t == nil || (t.IsPtr() && t.Elem() == nil) || t.IsUntyped() {
		base.Fatalf("TypeSym %v", t)
	}
	if t.Kind() == types.TFUNC && t.Recv() != nil {
		base.Fatalf("misuse of method type: %v", t)
	}
	s := types.TypeSym(t)
	signatmu.Lock()
	NeedRuntimeType(t)
	signatmu.Unlock()
	return s
}

func TypeLinksymPrefix(prefix string, t *types.Type) *obj.LSym {
	return TypeSymPrefix(prefix, t).Linksym()
}

func TypeLinksymLookup(name string) *obj.LSym {
	return types.TypeSymLookup(name).Linksym()
}

func TypeLinksym(t *types.Type) *obj.LSym {
	return TypeSym(t).Linksym()
}

func TypePtr(t *types.Type) *ir.AddrExpr {
	n := ir.NewLinksymExpr(base.Pos, TypeLinksym(t), types.Types[types.TUINT8])
	return typecheck.Expr(typecheck.NodAddr(n)).(*ir.AddrExpr)
}

// ITabLsym返回表示实现
// 接口iface。在typ不
// 实现iface的异常情况下，将创建一个虚拟选项卡。通常情况下，这种情况不会发生，因为typechecker会报告编译时错误。只有当类型断言或类型开关中的类型的目标类型被参数化时，这种情况才会发生，因此
// 它可能有时（但不总是）是无法实现指定
// 接口的类型。
func ITabLsym(typ, iface *types.Type) *obj.LSym {
	s, existed := ir.Pkgs.Itab.LookupOK(typ.LinkString() + "," + iface.LinkString())
	lsym := s.Linksym()

	if !existed {
		writeITab(lsym, typ, iface, true)
	}
	return lsym
}

// ITabAddr返回一个表达式，表示实现接口iface的具体类型类型的itab 
// 的指针。
func ITabAddr(typ, iface *types.Type) *ir.AddrExpr {
	s, existed := ir.Pkgs.Itab.LookupOK(typ.LinkString() + "," + iface.LinkString())
	lsym := s.Linksym()

	if !existed {
		writeITab(lsym, typ, iface, false)
	}

	n := ir.NewLinksymExpr(base.Pos, lsym, types.Types[types.TUINT8])
	return typecheck.Expr(typecheck.NodAddr(n)).(*ir.AddrExpr)
}

// needkeyupdate报告是否使用t作为密钥更新映射
// 需要更新密钥。
func needkeyupdate(t *types.Type) bool {
	switch t.Kind() {
	case types.TBOOL, types.TINT, types.TUINT, types.TINT8, types.TUINT8, types.TINT16, types.TUINT16, types.TINT32, types.TUINT32,
		types.TINT64, types.TUINT64, types.TUINTPTR, types.TPTR, types.TUNSAFEPTR, types.TCHAN:
		return false

	case types.TFLOAT32, types.TFLOAT64, types.TCOMPLEX64, types.TCOMPLEX128, // 浮点数和复数可以是+0/-0 
		types.TINTER,
		types.TSTRING: // 字符串可能具有较小的支持存储
		return true

	case types.TARRAY:
		return needkeyupdate(t.Elem())

	case types.TSTRUCT:
		for _, t1 := range t.Fields().Slice() {
			if needkeyupdate(t1.Type) {
				return true
			}
		}
		return false

	default:
		base.Fatalf("bad type for map key: %v", t)
		return true
	}
}

// hashMightPanic报告t类型的映射键的哈希是否会死机。
func hashMightPanic(t *types.Type) bool {
	switch t.Kind() {
	case types.TINTER:
		return true

	case types.TARRAY:
		return hashMightPanic(t.Elem())

	case types.TSTRUCT:
		for _, t1 := range t.Fields().Slice() {
			if hashMightPanic(t1.Type) {
				return true
			}
		}
		return false

	default:
		return false
	}
}

// formalType将预先声明的别名替换为实类型。
// 它们在内部是分开的，以使错误消息
// 更好，但我们必须将它们合并到反射表中。
func formalType(t *types.Type) *types.Type {
	switch t {
	case types.AnyType, types.ByteType, types.RuneType:
		return types.Types[t.Kind()]
	}
	return t
}

func writeType(t *types.Type) *obj.LSym {
	t = formalType(t)
	if t.IsUntyped() || t.HasTParam() {
		base.Fatalf("writeType %v", t)
	}

	s := types.TypeSym(t)
	lsym := s.Linksym()
	if s.Siggen() {
		return lsym
	}
	s.SetSiggen(true)

	// 特例（查看下面的运行时）：
	// 编译包运行时时，
	// 发出int、float等的类型结构。
	tbase := t

	if t.IsPtr() && t.Sym() == nil && t.Elem().Sym() != nil {
		tbase = t.Elem()
	}
	if tbase.Kind() == types.TFORW {
		base.Fatalf("unresolved defined type: %v", tbase)
	}

	if !NeedEmit(tbase) {
		if i := typecheck.BaseTypeIndex(t); i >= 0 {
			lsym.Pkg = tbase.Sym().Pkg.Prefix
			lsym.SymIdx = int32(i)
			lsym.Set(obj.AttrIndexed, true)
		}

		// TODO（mdempsky）：调查这种情况是否仍然发生。
		// 如果我们知道不需要为一个类型发出代码，
		// 我们应该为它建立一个链接符号索引。
		// 另请参见NeedEmit中的TODO。
		return lsym
	}

	ot := 0
	switch t.Kind() {
	default:
		ot = dcommontype(lsym, t)
		ot = dextratype(lsym, ot, t, 0)

	case types.TARRAY:
		// 运行时/类型。go:/arrayType 
		s1 := writeType(t.Elem())
		t2 := types.NewSlice(t.Elem())
		s2 := writeType(t2)
		ot = dcommontype(lsym, t)
		ot = objw.SymPtr(lsym, ot, s1, 0)
		ot = objw.SymPtr(lsym, ot, s2, 0)
		ot = objw.Uintptr(lsym, ot, uint64(t.NumElem()))
		ot = dextratype(lsym, ot, t, 0)

	case types.TSLICE:
		// 运行时/类型。go:/sliceType 
		s1 := writeType(t.Elem())
		ot = dcommontype(lsym, t)
		ot = objw.SymPtr(lsym, ot, s1, 0)
		ot = dextratype(lsym, ot, t, 0)

	case types.TCHAN:
		// 运行时/类型。go:/chanType 
		s1 := writeType(t.Elem())
		ot = dcommontype(lsym, t)
		ot = objw.SymPtr(lsym, ot, s1, 0)
		ot = objw.Uintptr(lsym, ot, uint64(t.ChanDir()))
		ot = dextratype(lsym, ot, t, 0)

	case types.TFUNC:
		for _, t1 := range t.Recvs().Fields().Slice() {
			writeType(t1.Type)
		}
		isddd := false
		for _, t1 := range t.Params().Fields().Slice() {
			isddd = t1.IsDDD()
			writeType(t1.Type)
		}
		for _, t1 := range t.Results().Fields().Slice() {
			writeType(t1.Type)
		}

		ot = dcommontype(lsym, t)
		inCount := t.NumRecvs() + t.NumParams()
		outCount := t.NumResults()
		if isddd {
			outCount |= 1 << 15
		}
		ot = objw.Uint16(lsym, ot, uint16(inCount))
		ot = objw.Uint16(lsym, ot, uint16(outCount))
		if types.PtrSize == 8 {
			ot += 4 // align for*rtype 
		}

		dataAdd := (inCount + t.NumResults()) * types.PtrSize
		ot = dextratype(lsym, ot, t, dataAdd)

		// rtype指针数组跟随funcType。
		for _, t1 := range t.Recvs().Fields().Slice() {
			ot = objw.SymPtr(lsym, ot, writeType(t1.Type), 0)
		}
		for _, t1 := range t.Params().Fields().Slice() {
			ot = objw.SymPtr(lsym, ot, writeType(t1.Type), 0)
		}
		for _, t1 := range t.Results().Fields().Slice() {
			ot = objw.SymPtr(lsym, ot, writeType(t1.Type), 0)
		}

	case types.TINTER:
		m := imethods(t)
		n := len(m)
		for _, a := range m {
			writeType(a.type_)
		}

		// 运行时/类型。go:/interfaceType 
		ot = dcommontype(lsym, t)

		var tpkg *types.Pkg
		if t.Sym() != nil && t != types.Types[t.Kind()] && t != types.ErrorType {
			tpkg = t.Sym().Pkg
		}
		ot = dgopkgpath(lsym, ot, tpkg)

		ot = objw.SymPtr(lsym, ot, lsym, ot+3*types.PtrSize+uncommonSize(t))
		ot = objw.Uintptr(lsym, ot, uint64(n))
		ot = objw.Uintptr(lsym, ot, uint64(n))
		dataAdd := imethodSize() * n
		ot = dextratype(lsym, ot, t, dataAdd)

		for _, a := range m {
			// 运行时/类型。go:/i方法
			exported := types.IsExported(a.name.Name)
			var pkg *types.Pkg
			if !exported && a.name.Pkg != tpkg {
				pkg = a.name.Pkg
			}
			nsym := dname(a.name.Name, "", pkg, exported)

			ot = objw.SymPtrOff(lsym, ot, nsym)
			ot = objw.SymPtrOff(lsym, ot, writeType(a.type_))
		}

	// 运行时/类型。go:/mapType 
	case types.TMAP:
		s1 := writeType(t.Key())
		s2 := writeType(t.Elem())
		s3 := writeType(MapBucketType(t))
		hasher := genhash(t.Key())

		ot = dcommontype(lsym, t)
		ot = objw.SymPtr(lsym, ot, s1, 0)
		ot = objw.SymPtr(lsym, ot, s2, 0)
		ot = objw.SymPtr(lsym, ot, s3, 0)
		ot = objw.SymPtr(lsym, ot, hasher, 0)
		var flags uint32
		// 注意：标志必须与../..
		// 和../....中的maptype builder/思考/打字。go:MapOf。
		if t.Key().Size() > MAXKEYSIZE {
			ot = objw.Uint8(lsym, ot, uint8(types.PtrSize))
			flags |= 1 // 间接键
		} else {
			ot = objw.Uint8(lsym, ot, uint8(t.Key().Size()))
		}

		if t.Elem().Size() > MAXELEMSIZE {
			ot = objw.Uint8(lsym, ot, uint8(types.PtrSize))
			flags |= 2 // 间接值
		} else {
			ot = objw.Uint8(lsym, ot, uint8(t.Elem().Size()))
		}
		ot = objw.Uint16(lsym, ot, uint16(MapBucketType(t).Size()))
		if types.IsReflexive(t.Key()) {
			flags |= 4 // 自反键
		}
		if needkeyupdate(t.Key()) {
			flags |= 8 // 需要密钥更新
		}
		if hashMightPanic(t.Key()) {
			flags |= 16 // 哈希可能会导致死机
		}
		ot = objw.Uint32(lsym, ot, flags)
		ot = dextratype(lsym, ot, t, 0)
		if u := t.Underlying(); u != t {
			// 如果t是命名映射类型，也将底层映射
			// 类型保留在二进制文件中。这对于确保命名映射和通过
			// 反射转换为其基础类型的同一映射使用相同的哈希函数非常重要。见第37716期。
			r := obj.Addrel(lsym)
			r.Sym = writeType(u)
			r.Type = objabi.R_KEEP
		}

	case types.TPTR:
		if t.Elem().Kind() == types.TANY {
			// 运行时/类型。go:/Unsafepointer类型
			ot = dcommontype(lsym, t)
			ot = dextratype(lsym, ot, t, 0)

			break
		}

		// 运行时/类型。go:/p键入
		s1 := writeType(t.Elem())

		ot = dcommontype(lsym, t)
		ot = objw.SymPtr(lsym, ot, s1, 0)
		ot = dextratype(lsym, ot, t, 0)

	// 运行时/类型。go:/structype 
	// 为安全起见，仅导出字段。
	case types.TSTRUCT:
		fields := t.Fields().Slice()
		for _, t1 := range fields {
			writeType(t1.Type)
		}

		// 结构中所有未导出的结构字段名
		// 类型必须来自单个包。通过在
		// 结构类型描述符中标识并记录该包，我们可以从字段描述符中省略该信息。
		var spkg *types.Pkg
		for _, f := range fields {
			if !types.IsExported(f.Sym.Name) {
				spkg = f.Sym.Pkg
				break
			}
		}

		ot = dcommontype(lsym, t)
		ot = dgopkgpath(lsym, ot, spkg)
		ot = objw.SymPtr(lsym, ot, lsym, ot+3*types.PtrSize+uncommonSize(t))
		ot = objw.Uintptr(lsym, ot, uint64(len(fields)))
		ot = objw.Uintptr(lsym, ot, uint64(len(fields)))

		dataAdd := len(fields) * structfieldSize()
		ot = dextratype(lsym, ot, t, dataAdd)

		for _, f := range fields {
			// 运行时/类型。go:/structField 
			ot = dnameField(lsym, ot, spkg, f)
			ot = objw.SymPtr(lsym, ot, writeType(f.Type), 0)
			offsetAnon := uint64(f.Offset) << 1
			if offsetAnon>>1 != uint64(f.Offset) {
				base.Fatalf("%v: bad field offset for %s", t, f.Sym.Name)
			}
			if f.Embedded != 0 {
				offsetAnon |= 1
			}
			ot = objw.Uintptr(lsym, ot, offsetAnon)
		}
	}

	ot = dextratypeData(lsym, ot, t)
	objw.Global(lsym, int32(ot), int16(obj.DUPOK|obj.RODATA))
	// 注意：需要DUPOK来确保给定类型的
	// 类型描述符不会超过一个。

	// 链接器将在二进制文件中为
	// 类型保留一个包含所有类型链接的表，以便运行时可以找到它们。
	// 
	// 当buildmode=shared时，所有类型都在类型链接中，因此
	// 运行时可以消除重复类型指针。
	keep := base.Ctxt.Flag_dynlink
	if !keep && t.Sym() == nil {
		// 对于一个未命名的类型，我们只需要在reflect可以在运行时创建该类型时才需要该链接。PtrTo和类似
		// 函数。如果程序中存在该类型，则那些
		// 函数必须返回现有的类型结构，而不是创建新的类型结构。
		switch t.Kind() {
		case types.TPTR, types.TARRAY, types.TCHAN, types.TFUNC, types.TMAP, types.TSLICE, types.TSTRUCT:
			keep = true
		}
	}
	// 不要在typelinks中放置Noalg类型。见第22605期。
	if types.TypeHasNoAlg(t) {
		keep = false
	}
	lsym.Set(obj.AttrMakeTypelink, keep)

	return lsym
}

// InterfaceMethodOffset返回接口
// 类型描述符ityp中第i个方法的偏移量。
func InterfaceMethodOffset(ityp *types.Type, i int64) int64 {
	// 接口类型描述符布局为struct{
	// /_-type 
	// /pkgpath 
	// /[]i方法
	// /取消命名
	// /[]i方法
	// }
	// /i方法的大小是8。
	return int64(commonSize()+4*types.PtrSize+uncommonSize(ityp)) + i*8
}

// NeedRuntimeType确保为t发出运行时类型描述符。
func NeedRuntimeType(t *types.Type) {
	if t.HasTParam() {
		// 泛型类型在运行时并不存在，也没有运行时
		// 类型描述符。但我们确实写出了形状类型。
		return
	}
	if _, ok := signatset[t]; !ok {
		signatset[t] = struct{}{}
		signatslice = append(signatslice, typeAndStr{t: t, short: types.TypeSymName(t), regular: t.String()})
	}
}

func WriteRuntimeTypes() {
	// 处理signatslice。使用循环，因为writeType在处理signatslice时会向其添加
	// 条目。
	for len(signatslice) > 0 {
		signats := signatslice
		// 对可复制版本进行排序。
		sort.Sort(typesByString(signats))
		for _, ts := range signats {
			t := ts.t
			writeType(t)
			if t.Sym() != nil {
				writeType(types.NewPtr(t))
			}
		}
		signatslice = signatslice[len(signats):]
	}

	// 发出GC数据符号。
	gcsyms := make([]typeAndStr, 0, len(gcsymset))
	for t := range gcsymset {
		gcsyms = append(gcsyms, typeAndStr{t: t, short: types.TypeSymName(t), regular: t.String()})
	}
	sort.Sort(typesByString(gcsyms))
	for _, ts := range gcsyms {
		dgcsym(ts.t, true)
	}
}

// WriteTab为实现接口iface的具体类型类型编写itab。如果
// AllowNoImplement为true，则允许typ不实现iface的情况，只需
// 创建一个带有调零方法项的伪itab。
func writeITab(lsym *obj.LSym, typ, iface *types.Type, allowNonImplement bool) {
	// TODO（mdempsky）：修复methodWrapper、geneq和genhash（可能还有
	// /其他）以停止对它们的破坏。
	oldpos, oldfn := base.Pos, ir.CurFunc
	defer func() { base.Pos, ir.CurFunc = oldpos, oldfn }()

	if typ == nil || (typ.IsPtr() && typ.Elem() == nil) || typ.IsUntyped() || iface == nil || !iface.IsInterface() || iface.IsEmptyInterface() {
		base.Fatalf("writeITab(%v, %v)", typ, iface)
	}

	sigs := iface.AllMethods().Slice()
	entries := make([]*obj.LSym, 0, len(sigs))

	// SIG和方法都按名称排序，
	// 因此我们可以在单通道中找到交叉点
	for _, m := range methods(typ) {
		if m.name == sigs[0].Sym {
			entries = append(entries, m.isym)
			if m.isym == nil {
				panic("NO ISYM")
			}
			sigs = sigs[1:]
			if len(sigs) == 0 {
				break
			}
		}
	}
	completeItab := len(sigs) == 0
	if !allowNonImplement && !completeItab {
		base.Fatalf("incomplete itab")
	}

	// 将空itab符号转储到i.sym 
	// 类型itab结构{
	// inter*接口类型
	// /_type*_type
	// 哈希uint32 
	// /}
	o := objw.SymPtr(lsym, 0, writeType(iface), 0)
	o = objw.SymPtr(lsym, o, writeType(typ), 0)
	o = objw.Uint32(lsym, o, types.TypeHash(typ)) // 类型哈希的副本
	o += 4                                        // 跳过未使用的字段
	for _, fn := range entries {
		if !completeItab {
			// 如果typ未实现iface，则将方法项设为零。
			o = objw.Uintptr(lsym, o, 0)
		} else {
			o = objw.SymPtrWeak(lsym, o, fn, 0) // 每个方法的方法指针
		}
	}
	// 没有任何内容写入静态ITAB，因此它们是只读的。
	objw.Global(lsym, int32(o), int16(obj.DUPOK|obj.RODATA))
	lsym.Set(obj.AttrContentAddressable, true)
}

func WriteTabs() {
	// 处理ptab 
	if types.LocalPkg.Name == "main" && len(ptabs) > 0 {
		ot := 0
		s := base.Ctxt.Lookup("go.plugin.tabs")
		for _, p := range ptabs {
			// 将ptab符号转储到go中。pluginsym包。
			// 
			// type ptab struct{
			// name nameOff 
			// typ typeOff 
			// /}
			nsym := dname(p.Sym().Name, "", nil, true)
			t := p.Type()
			if p.Class != ir.PFUNC {
				t = types.NewPtr(t)
			}
			tsym := writeType(t)
			ot = objw.SymPtrOff(s, ot, nsym)
			ot = objw.SymPtrOff(s, ot, tsym)
			// 插件将符号导出为接口。将它们的型号
			// 标记为用于生产。
			tsym.Set(obj.AttrUsedInIface, true)
		}
		objw.Global(s, int32(ot), int16(obj.RODATA))

		ot = 0
		s = base.Ctxt.Lookup("go.plugin.exports")
		for _, p := range ptabs {
			ot = objw.SymPtr(s, ot, p.Linksym(), 0)
		}
		objw.Global(s, int32(ot), int16(obj.RODATA))
	}
}

func WriteImportStrings() {
	// 为导入的包生成导入字符串
	for _, p := range types.ImportedPkgList() {
		dimportpath(p)
	}
}

func WriteBasicTypes() {
	// 在编译包运行时执行基本类型。
	// 它们必须至少在一个包中，
	// 并且运行时总是隐式加载，
	// 所以这和任何包一样好。
	// 另一个可能的选择是package main，
	// 但使用runtime意味着目标文件中的副本更少。
	if base.Ctxt.Pkgpath == "runtime" {
		for i := types.Kind(1); i <= types.TBOOL; i++ {
			writeType(types.NewPtr(types.Types[i]))
		}
		writeType(types.NewPtr(types.Types[types.TSTRING]))
		writeType(types.NewPtr(types.Types[types.TUNSAFEPTR]))
		if base.Flag.G > 0 {
			writeType(types.AnyType)
		}

		// emit type structs for error和func（error）string。
		// 后者是自动生成的包装类型。
		writeType(types.NewPtr(types.ErrorType))

		writeType(types.NewSignature(types.NoPkg, nil, nil, []*types.Field{
			types.NewField(base.Pos, nil, types.ErrorType),
		}, []*types.Field{
			types.NewField(base.Pos, nil, types.Types[types.TSTRING]),
		}))

		// 为runtime和main添加路径，6l隐式导入。
		dimportpath(ir.Pkgs.Runtime)

		if base.Flag.Race {
			dimportpath(types.NewPkg("runtime/race", ""))
		}
		if base.Flag.MSan {
			dimportpath(types.NewPkg("runtime/msan", ""))
		}
		if base.Flag.ASan {
			dimportpath(types.NewPkg("runtime/asan", ""))
		}

		dimportpath(types.NewPkg("main", ""))
	}
}

type typeAndStr struct {
	t       *types.Type
	short   string // “short”在这里的意思是TypeSymName 
	regular string
}

type typesByString []typeAndStr

func (a typesByString) Len() int { return len(a) }
func (a typesByString) Less(i, j int) bool {
	if a[i].short != a[j].short {
		return a[i].short < a[j].short
	}
	// 当两个类型之间的唯一区别是
	// 它们指的是字节还是uint8，例如**byte vs**uint8，
	// 类型的名称字符串可以相同。
	// 要保留确定性排序顺序，请按字符串（）对其排序。
	// 
	// TODO（mdempsky）：这一切似乎都很可疑。使用LinkString将避免命名冲突，也不应该有理由关心“byte”和“uint8”的区别：它们共享相同的运行时类型
	// 描述符。
	if a[i].regular != a[j].regular {
		return a[i].regular < a[j].regular
	}
	// 在不同位置定义的相同匿名接口
	// 在上述检查中是相同的，但在DWARF输出中是不同的。
	// 按源位置排序，以确保确定的顺序。
	// 见第27013期和第30202期。
	if a[i].t.Kind() == types.TINTER && a[i].t.AllMethods().Len() > 0 {
		return a[i].t.AllMethods().Index(0).Pos.Before(a[j].t.AllMethods().Index(0).Pos)
	}
	return false
}
func (a typesByString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

// maxPtrmaskBytes是GC ptrmask位图的最大长度，
// 它包含描述给定类型中指针位置的1位条目。
// 在这个长度以上，GC信息被记录为一个GC程序，
// 可以简洁地表示重复。在这两种形式中，运行时使用
// 信息初始化堆位图，
// 对于大型类型（如128个或更多单词），它们的速度大致与
// 相同。GC程序从来都不会太大，而且通常都是
// 紧凑型的。（如果涉及大型阵列，它们可以任意
// 更紧凑。）
// 
// 截止必须足够大，以使任何足够大的分配
// 使用GC程序都足够大，以至于它不会与任何其他对象共享堆位图
// 字节，从而允许GC程序执行
// 假定一个对齐的启动，而不使用原子操作。在当前的
// 运行时中，这意味着所有大于截止值的malloc大小的类必须是四个字的倍数。在16字节的32位系统上，
// 所有大小类>=16字节都是16字节对齐的，因此没有真正的约束。
// 在64位系统上，即32字节，对于大于等于256字节的大小类，保证32字节对齐。在64位系统上，分配给
// 的256个字节是32个指针，其位可容纳4个字节。所以maxPtrmaskBytes 
// 必须大于等于4。
// 
// 我们以前使用16，因为GC程序确实有一些固定的开销
// 开始，处理128个指针似乎足以将这些开销很好地分摊。
// 
// 为了确保运行时的chansend可以调用typeBitsBulkBarrier，
// 我们将限制提高到2048，这样即使是32位系统也可以保证对大小不超过64KB的对象使用位图。
// 
// 也被称为反射/类型。去
// 
const maxPtrmaskBytes = 2048

// GCSym返回一个包含t型GC信息的数据符号，以及一个布尔值，报告是否应在
// type kind中设置UseGCProg位，以及要记录在reflect type信息中的ptrdata字段。
// GCSym可以在并发后端调用，因此它不会发出符号
// content。
func GCSym(t *types.Type) (lsym *obj.LSym, useGCProg bool, ptrdata int64) {
	// 记录我们需要发出GC符号。
	gcsymmu.Lock()
	if _, ok := gcsymset[t]; !ok {
		gcsymset[t] = struct{}{}
	}
	gcsymmu.Unlock()

	return dgcsym(t, false)
}

// dgcsym返回一个包含类型t的GC信息的数据符号，以及一个布尔值，报告是否应在
// type kind中设置UseGCProg位，以及要记录在反射类型信息中的ptrdata字段。
// 当write为true时，它写入符号数据。
func dgcsym(t *types.Type, write bool) (lsym *obj.LSym, useGCProg bool, ptrdata int64) {
	ptrdata = types.PtrDataSize(t)
	if ptrdata/int64(types.PtrSize) <= maxPtrmaskBytes*8 {
		lsym = dgcptrmask(t, write)
		return
	}

	useGCProg = true
	lsym, ptrdata = dgcprog(t, write)
	return
}

// dgcptrmask发出并返回包含类型t的指针掩码的符号。
func dgcptrmask(t *types.Type, write bool) *obj.LSym {
	ptrmask := make([]byte, (types.PtrDataSize(t)/int64(types.PtrSize)+7)/8)
	fillptrmask(t, ptrmask)
	p := fmt.Sprintf("runtime.gcbits.%x", ptrmask)

	lsym := base.Ctxt.Lookup(p)
	if write && !lsym.OnList() {
		for i, x := range ptrmask {
			objw.Uint8(lsym, i, x)
		}
		objw.Global(lsym, int32(len(ptrmask)), obj.DUPOK|obj.RODATA|obj.LOCAL)
		lsym.Set(obj.AttrContentAddressable, true)
	}
	return lsym
}

// fillptrmask使用与保存指针的
// t中的字偏移量对应的1填充ptrmask。
// ptrmak被认为至少适合类型。PtrDataSize（t）/PtrSize位。
func fillptrmask(t *types.Type, ptrmask []byte) {
	for i := range ptrmask {
		ptrmask[i] = 0
	}
	if !t.HasPointers() {
		return
	}

	vec := bitvec.New(8 * int32(len(ptrmask)))
	typebits.Set(t, 0, vec)

	nptr := types.PtrDataSize(t) / int64(types.PtrSize)
	for i := int64(0); i < nptr; i++ {
		if vec.Get(int32(i)) {
			ptrmask[i/8] |= 1 << (uint(i) % 8)
		}
	}
}

// dgcprog发出并返回包含t型GC程序的符号
// 以及程序描述的数据大小（范围为
// /[types.PtrDataSize（t），t.Width]）。
// 实际上，大小是类型。PtrDataSize（t），非平凡数组除外。
// 对于非平凡数组，该程序描述了完整的t.宽度大小。
func dgcprog(t *types.Type, write bool) (*obj.LSym, int64) {
	types.CalcSize(t)
	if t.Size() == types.BADWIDTH {
		base.Fatalf("dgcprog: %v badwidth", t)
	}
	lsym := TypeLinksymPrefix(".gcprog", t)
	var p gcProg
	p.init(lsym, write)
	p.emit(t, 0)
	offset := p.w.BitIndex() * int64(types.PtrSize)
	p.end()
	if ptrdata := types.PtrDataSize(t); offset < ptrdata || offset > t.Size() {
		base.Fatalf("dgcprog: %v: offset=%d but ptrdata=%d size=%d", t, offset, ptrdata, t.Size())
	}
	return lsym, offset
}

type gcProg struct {
	lsym   *obj.LSym
	symoff int
	w      gcprog.Writer
	write  bool
}

func (p *gcProg) init(lsym *obj.LSym, write bool) {
	p.lsym = lsym
	p.write = write && !lsym.OnList()
	p.symoff = 4 // 前4个字节包含程序长度
	if !write {
		p.w.Init(func(byte) {})
		return
	}
	p.w.Init(p.writeByte)
	if base.Debug.GCProg > 0 {
		fmt.Fprintf(os.Stderr, "compile: start GCProg for %v\n", lsym)
		p.w.Debug(os.Stderr)
	}
}

func (p *gcProg) writeByte(x byte) {
	p.symoff = objw.Uint8(p.lsym, p.symoff, x)
}

func (p *gcProg) end() {
	p.w.End()
	if !p.write {
		return
	}
	objw.Uint32(p.lsym, 0, uint32(p.symoff-4))
	objw.Global(p.lsym, int32(p.symoff), obj.DUPOK|obj.RODATA|obj.LOCAL)
	p.lsym.Set(obj.AttrContentAddressable, true)
	if base.Debug.GCProg > 0 {
		fmt.Fprintf(os.Stderr, "compile: end GCProg for %v\n", p.lsym)
	}
}

func (p *gcProg) emit(t *types.Type, offset int64) {
	types.CalcSize(t)
	if !t.HasPointers() {
		return
	}
	if t.Size() == int64(types.PtrSize) {
		p.w.Ptr(offset / int64(types.PtrSize))
		return
	}
	switch t.Kind() {
	default:
		base.Fatalf("gcProg.emit: unexpected type %v", t)

	case types.TSTRING:
		p.w.Ptr(offset / int64(types.PtrSize))

	case types.TINTER:
		// 注意：第一个字不是指针。请参阅typebits中的注释。Set 
		p.w.Ptr(offset/int64(types.PtrSize) + 1)

	case types.TSLICE:
		p.w.Ptr(offset / int64(types.PtrSize))

	case types.TARRAY:
		if t.NumElem() == 0 {
			// 应该通过haspointers检查来处理
			base.Fatalf("gcProg.emit: empty array")
		}

		// 通过乘以计数将数组的数组展平为一个大数组。
		count := t.NumElem()
		elem := t.Elem()
		for elem.IsArray() {
			count *= elem.NumElem()
			elem = elem.Elem()
		}

		if !p.w.ShouldRepeat(elem.Size()/int64(types.PtrSize), count) {
			// 只发射比特更便宜。
			for i := int64(0); i < count; i++ {
				p.emit(elem, offset+i*elem.Size())
			}
			return
		}
		p.emit(elem, offset)
		p.w.ZeroUntil((offset + elem.Size()) / int64(types.PtrSize))
		p.w.Repeat(elem.Size()/int64(types.PtrSize), count-1)

	case types.TSTRUCT:
		for _, t1 := range t.Fields().Slice() {
			p.emit(t1.Type, offset+t1.Offset)
		}
	}
}

// ZeroAddr返回一个符号的地址，其中至少包含
// size字节的零。
func ZeroAddr(size int64) ir.Node {
	if size >= 1<<31 {
		base.Fatalf("map elem too big %d", size)
	}
	if ZeroSize < size {
		ZeroSize = size
	}
	lsym := base.PkgLinksym("go.map", "zero", obj.ABI0)
	x := ir.NewLinksymExpr(base.Pos, lsym, types.Types[types.TUINT8])
	return typecheck.Expr(typecheck.NodAddr(x))
}

func CollectPTabs() {
	if !base.Ctxt.Flag_dynlink || types.LocalPkg.Name != "main" {
		return
	}
	for _, exportn := range typecheck.Target.Exports {
		s := exportn.Sym()
		nn := ir.AsNode(s.Def)
		if nn == nil {
			continue
		}
		if nn.Op() != ir.ONAME {
			continue
		}
		n := nn.(*ir.Name)
		if !types.IsExported(s.Name) {
			continue
		}
		if s.Pkg.Name != "main" {
			continue
		}
		ptabs = append(ptabs, n)
	}
}

// NeedEmit报告typ是否是我们需要为其发出代码的类型（例如，运行时类型描述符、方法包装器）。
func NeedEmit(typ *types.Type) bool {
	// TODO（mdempsky）：导出数据应该跟踪发出了哪些匿名
	// 和实例化类型，因此至少下游
	// 包可以跳过重新发出它们。
	// 
	// 也许我们可以将链接器符号索引推广到
	// 跟踪任意类型的索引，而不仅仅是定义的类型，
	// 使用它的存在来检测这一点。同样的想法也适用于
	// 实例化的泛型函数。

	switch sym := typ.Sym(); {
	case sym == nil:
		// 匿名类型；可能以前从未见过，也可能再也没有见过。
		// 为了安全，需要发射（但是，请参见上面的TODO）。
		return true

	case sym.Pkg == types.LocalPkg:
		// 本地定义类型；我们的责任。
		return true

	case base.Ctxt.Pkgpath == "runtime" && (sym.Pkg == types.BuiltinPkg || sym.Pkg == types.UnsafePkg):
		// 包运行时负责包含内置
		// 类型（预声明和包不安全）的代码。
		return true

	case typ.IsFullyInstantiated():
		// 实例化类型；可能是用唯一的类型参数实例化的。
		// 为了安全，需要排放（不过，请参见上面的TODO）。
		return true

	case typ.HasShape():
		// 形状类型；即使它生活在空气中也需要排放。塑形包装。
		// TODO:确保链接器消除了重复数据（请参阅上面writeType中的dupok）。
		return true

	default:
		// 应该是由进口的软件包发出的。
		return false
	}
}

// 生成一个包装函数，将T型接收器转换为U型接收器。
// 也就是说，
// 
// func（T）M（）{
// …
// }
// 
// 已经存在；此函数生成
// 
// func（u）M（）{
// u.M（）
// }
// 
// 其中T和U类型使得U.M（）是有效的
// 并调用T.M方法。
// 结果函数用于方法表。
// 
// rcvr-U 
// method-M func（t）（），一个TFIELD类型结构
// 
// 还对实例化泛型类型上的方法进行包装，以便在itab条目中使用。
// 对于实例化的泛型类型G[int]，我们生成如下包装器：
// G[int]指针形状：
// func（xG[int]）f（arg）{
// .inst.G[int].f（dictionary，x，arg）
func methodWrapper(rcvr *types.Type, method *types.Field, forItab bool) *obj.LSym {
	orig := rcvr
	if forItab && !types.IsDirectIface(rcvr) {
		rcvr = rcvr.PtrTo()
	}

	generic := false
	// 如果我们要实现一个方法（可能通过一个
	// 嵌入字段），这是一个接口方法，我们不需要字典。
	if !types.IsInterfaceMethod(method.Type) {
		rcvr1 := deref(rcvr)
		if len(rcvr1.RParams()) > 0 {
			// 如果rcvr有rparams，请记住方法是泛型的，这意味着我们需要向包装器添加一个字典。
			generic = true
			if rcvr.HasShape() {
				base.Fatalf("method on type instantiated with shapes, rcvr:%+v", rcvr)
			}
		}
	}

	newnam := ir.MethodSym(rcvr, method.Sym)
	lsym := newnam.Linksym()
	if newnam.Siggen() {
		return lsym
	}
	newnam.SetSiggen(true)

	// 除了在怪癖模式下，unified IR创建自己的包装器。
	if base.Debug.Unified != 0 && base.Debug.UnifiedQuirks == 0 {
		return lsym
	}

	methodrcvr := method.Type.Recv().Type
	// 对于泛型方法，即使接收方
	// 类型相同，我们也需要生成包装器，因为我们要添加字典。
	if !generic && types.Identical(rcvr, methodrcvr) {
		return lsym
	}

	if !NeedEmit(rcvr) || rcvr.IsPtr() && !NeedEmit(rcvr.Elem()) {
		return lsym
	}

	base.Pos = base.AutogeneratedPos
	typecheck.DeclContext = ir.PEXTERN

	tfn := ir.NewFuncType(base.Pos,
		ir.NewField(base.Pos, typecheck.Lookup(".this"), nil, rcvr),
		typecheck.NewFuncParams(method.Type.Params(), true),
		typecheck.NewFuncParams(method.Type.Results(), false))

	// TODO（奥斯汀）：SelectorExpr可能创建了一个或多个
	// ir。这些名称已包含nil Func字段。我们应该把这些合并起来，并在名字后面加上Func。abc6t.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g.abc6g。
	fn := typecheck.DeclFunc(newnam, tfn)
	fn.SetDupok(true)

	nthis := ir.AsNode(tfn.Type().Recv().Nname)

	indirect := rcvr.IsPtr() && rcvr.Elem() == methodrcvr

	if indirect {
		n := ir.NewIfStmt(base.Pos, nil, nil, nil)
		n.Cond = ir.NewBinaryExpr(base.Pos, ir.OEQ, nthis, typecheck.NodNil())
		call := ir.NewCallExpr(base.Pos, ir.OCALL, typecheck.LookupRuntime("panicwrap"), nil)
		n.Body = []ir.Node{call}
		fn.Body.Append(n)
	}

	dot := typecheck.AddImplicitDots(ir.NewSelectorExpr(base.Pos, ir.OXDOT, nthis, method.Sym))
	// 糟糕的情况是，对包装器进行本地调用：包装器将
	// 调用可能位于不同模块中的实现，因此将
	// TOC设置为该模块的适当值。但是，如果它直接将
	// 返回给包装器的调用者，则不会将其重置为该函数的正确
	// 值。
	if !base.Flag.Cfg.Instrumenting && rcvr.IsPtr() && methodrcvr.IsPtr() && method.Embedded != 0 && !types.IsInterfaceMethod(method.Type) && !(base.Ctxt.Arch.Name == "ppc64le" && base.Ctxt.Flag_dynlink) && !generic {
		call := ir.NewCallExpr(base.Pos, ir.OCALL, dot, nil)
		call.Args = ir.ParamNames(tfn.Type())
		call.IsDDD = tfn.Type().IsVariadic()
		fn.Body.Append(ir.NewTailCallStmt(base.Pos, call))
	} else {
		fn.SetWrapper(true) // 忽略帧以便恐慌+恢复匹配
		var call *ir.CallExpr

		if generic && dot.X != nthis {
			// 如果涉及嵌入，那么我们应该执行下面的
			// 普通非泛型嵌入包装，它调用
			// 实际接收器类型的包装，使用点作为
			// 参数。这个包装器不需要一般处理（添加
			// 字典）。
			generic = false
		}

		if generic {
			targs := deref(rcvr).RParams()
			// 自动生成的指针/非指针
			// 接收器方法的包装器应与
			// 相应的原始（用户编写的）方法共享同一个字典。
			baseOrig := orig
			if baseOrig.IsPtr() && !methodrcvr.IsPtr() {
				baseOrig = baseOrig.Elem()
			} else if !baseOrig.IsPtr() && methodrcvr.IsPtr() {
				baseOrig = types.NewPtr(baseOrig)
			}
			args := []ir.Node{getDictionary(ir.MethodSym(baseOrig, method.Sym), targs)}
			if indirect {
				args = append(args, ir.NewStarExpr(base.Pos, dot.X))
			} else if methodrcvr.IsPtr() && methodrcvr.Elem() == dot.X.Type() {
				// 方法调用通过非指针
				// 带有指针方法的嵌入字段的情况。
				args = append(args, typecheck.NodAddrAt(base.Pos, dot.X))
			} else {
				args = append(args, dot.X)
			}
			args = append(args, ir.ParamNames(tfn.Type())...)

			// 目标方法使用成形名称。
			targs2 := make([]*types.Type, len(targs))
			origRParams := deref(orig).OrigType().RParams()
			for i, t := range targs {
				targs2[i] = typecheck.Shapify(t, i, origRParams[i])
			}
			targs = targs2

			sym := typecheck.MakeFuncInstSym(ir.MethodSym(methodrcvr, method.Sym), targs, false, true)
			if sym.Def == nil {
				// 目前，我们通过在
				// 点头/模板。go:InstanceMethods 
				// 由于内联函数
				// 而产生的额外实例化应该已导出，因此可通过
				// 解析获得。
				in := typecheck.Resolve(ir.NewIdent(src.NoXPos, sym))
				if in.Op() == ir.ONONAME {
					base.Fatalf("instantiation %s not found", sym.Name)
				}
				sym = in.Sym()
			}
			target := ir.AsNode(sym.Def)
			call = ir.NewCallExpr(base.Pos, ir.OCALL, target, args)
			// 填入InstanceMethod中未填入
			// 的泛型方法节点。
			method.Nname = fn.Nname
		} else {
			call = ir.NewCallExpr(base.Pos, ir.OCALL, dot, nil)
			call.Args = ir.ParamNames(tfn.Type())
		}
		call.IsDDD = tfn.Type().IsVariadic()
		if method.Type.NumResults() > 0 {
			ret := ir.NewReturnStmt(base.Pos, nil)
			ret.Results = []ir.Node{call}
			fn.Body.Append(ret)
		} else {
			fn.Body.Append(call)
		}
	}

	typecheck.FinishFuncBody()
	if base.Debug.DclStack != 0 {
		types.CheckDclstack()
	}

	typecheck.Func(fn)
	ir.CurFunc = fn
	typecheck.Stmts(fn.Body)

	if AfterGlobalEscapeAnalysis {
		inline.InlineCalls(fn)
		escape.Batch([]*ir.Func{fn}, false)
	}

	ir.CurFunc = nil
	typecheck.Target.Decls = append(typecheck.Target.Decls, fn)

	return lsym
}

// AfterGlobalScapeAnalysis跟踪包gc是否已经
// 执行了主要的全局转义分析过程。如果是这样，
// methodWrapper负责对任何
// 生成的包装进行逃逸分析。
var AfterGlobalEscapeAnalysis bool

var ZeroSize int64

// MarkTypeUsedIn接口标记类型t转换为接口。
// 此信息用于链接器中的死方法消除。
func MarkTypeUsedInInterface(t *types.Type, from *obj.LSym) {
	if t.HasShape() {
		// 形状类型不应该放在接口中，所以我们不应该到这里。
		base.Fatalf("shape types have no methods %+v", t)
	}
	tsym := TypeLinksym(t)
	// 发射标记重新定位。如果可以访问“from”，则链接器将知道该类型已转换为接口。
	r := obj.Addrel(from)
	r.Sym = tsym
	r.Type = objabi.R_USEIFACE
}

// MarkUsedIfaceMethod标记当前
// 函数中使用了接口方法。n是ocalliner节点。
func MarkUsedIfaceMethod(n *ir.CallExpr) {
	// 跳过未命名函数（func（））
	if ir.CurFunc.LSym == nil {
		return
	}
	dot := n.X.(*ir.SelectorExpr)
	ityp := dot.X.Type()
	if ityp.HasShape() {
		// 这里我们在通用接口上调用一个方法。类似于：
		// 
		// type I[T any]接口{foo（）T}
		// func f[T any]（x I[T]）{
		// /..=x.foo（）
		// /}
		// /f[int]（…）
		// f[string]（…）
		// 
		// 在本例中，在f中，我们在通用接口上调用foo。
		// 这可能是哪种方法？通常我们可以通过名称和类型匹配
		// 方法。但是在本例中，我们并不知道我们调用的方法的类型。它可以是func（）int 
		// 或func（）字符串。所以我们只匹配函数名，而不是
		// 下面的非泛型情况下使用的名称和类型。
		// TODO:实例化至少知道实例化的
		// 类型的形状，链接器可以使用
		// 某种模糊形状匹配来进行更复杂的匹配。目前，只使用匹配方法的名称
		// 即可。
		r := obj.Addrel(ir.CurFunc.LSym)
		// 我们使用一个单独的符号来告诉链接器方法名。
		// /（在最终二进制中不需要符号本身。）
		r.Sym = staticdata.StringSym(src.NoXPos, dot.Sel.Name)
		r.Type = objabi.R_USEGENERICIFACEMETHOD
		return
	}

	tsym := TypeLinksym(ityp)
	r := obj.Addrel(ir.CurFunc.LSym)
	r.Sym = tsym
	// 点。Offset（）是方法索引*PtrSize（itab中代码指针
	// 的偏移量）。
	midx := dot.Offset() / int64(types.PtrSize)
	r.Add = InterfaceMethodOffset(ityp, midx)
	r.Type = objabi.R_USEIFACEMETHOD
}

// getDictionary返回给定命名泛型函数
// 或方法的字典，并带有给定的类型参数。
func getDictionary(gf *types.Sym, targs []*types.Type) ir.Node {
	if len(targs) == 0 {
		base.Fatalf("%s should have type arguments", gf.Name)
	}
	for _, t := range targs {
		if t.HasShape() {
			base.Fatalf("dictionary for %s should only use concrete types: %+v", gf.Name, t)
		}
	}

	sym := typecheck.MakeDictSym(gf, targs, true)

	// 应该已经由InstanceMethods（）生成了字典。
	// 内联函数所需的额外字典应已导出，因此可通过Resolve获得。
	if lsym := sym.Linksym(); len(lsym.P) == 0 {
		in := typecheck.Resolve(ir.NewIdent(src.NoXPos, sym))
		if in.Op() == ir.ONONAME {
			base.Fatalf("Dictionary should have already been generated: %s.%s", sym.Pkg.Path, sym.Name)
		}
		sym = in.Sym()
	}

	// 创建（或重用）引用字典符号的节点。
	var n *ir.Name
	if sym.Def != nil {
		n = sym.Def.(*ir.Name)
	} else {
		n = typecheck.NewName(sym)
		n.SetType(types.Types[types.TUINTPTR]) // 应该是[…]uintptr，但实际上并不重要
		n.SetTypecheck(1)
		n.Class = ir.PEXTERN
		sym.Def = n
	}

	// 返回字典的地址。
	np := typecheck.NodAddr(n)
	// 注意：将字典指针视为uintpttr，因此它们不是关于GC的指针。这节省了堆栈扫描工作、写入障碍等。
	// 我们可以摆脱它，因为字典是全局变量。
	np.SetType(types.Types[types.TUINTPTR])
	np.SetTypecheck(1)
	return np
}

func deref(t *types.Type) *types.Type {
	if t.IsPtr() {
		return t.Elem()
	}
	return t
}
